/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/

#include "workerprecachemap.h"

#include "image.h"

#define MAKE_CHUNK_KEY(xa12,ya12,za12) za12*100000+ya12*1000+xa12

WorkerPrecacheMap::WorkerPrecacheMap(const QString& filename,
                                     const QString& cbn,
                                     const QHash<int,int>& cc,
                                     const QHash<int,int>& cr ) :
    WorkerProcess(4)
{
    chunks_rows = cr;
    chunks_cols = cc;
    image_filename = filename;
    source_image = NULL;

    // Build filenames:
    foreach( int z, chunks_rows.keys() )
        for ( int chunk_row = 0; chunk_row < chunks_rows.value(z,0); chunk_row++ )
            for ( int chunk_col = 0; chunk_col < chunks_cols.value(z,0); chunk_col++ )
                chunk_filenames.insert( MAKE_CHUNK_KEY(chunk_col, chunk_row, z), cbn.arg( chunk_col)
                                                                                    .arg( chunk_row )
                                                                                    .arg( z ) );
}

WorkerPrecacheMap::~WorkerPrecacheMap()
{
    deallocateChunkImages();
}

bool WorkerPrecacheMap::do_stuff()
{
    // Check for image existance
    if ( !QFile::exists( image_filename ) )
    {
        setErrorString( tr("The map file must exists") );
        return false;
    }
    QFileInfo source_info( image_filename );
    source_timestamp = source_info.lastModified();
    int chunk_col,chunk_row,max_col;
    QString msg, tmp_filename;
    Image *tmp_image;
    uchar* scanline, *chunk_scanline;
    int output_height, output_width, remaining_rows, remaining_cols, current_row, tot_chunks;
    int processed;

    // If no chunks are missing, skip
    if ( !findMissing() )
        return true;

    // Otherwise, rebuild any missing chunks...
    // Iterate for all zoom levels
    int tot_zooms = chunks_rows.count();
    int cur_zoom = 1;
    foreach( int z, chunks_rows.keys() )
    {
        sendUpdate( tr("Zoom level") + QString(" %1 / %2").arg(cur_zoom).arg(tot_zooms), 1, 1, tot_zooms, cur_zoom );
        cur_zoom++;

        // Track processed chunks for status upgrade
        processed = 0;
        // Store maximum nunber of chunks for this zoom level
        max_col = chunks_cols.value(z,0);
        // How many chunks do we have to process on this pass?
        tot_chunks = chunks_rows.value(z,0) * max_col;

        msg = tr("Rebuilding ")+ "(%1/" + QString("%1 chunks").arg(tot_chunks) + ")";
        sendUpdate( msg.arg(0), 2, 0, tot_chunks, 0 );

        // Open source image...
        if ( source_image != NULL )
            deallocateChunkImages();
        source_image = Image::LoadImage( image_filename );
        // Get ready to read image data...
        source_image->startReading( z, output_width, output_height );

        // Force to open all destination images on first loop
        current_row = CHUNK_SIZE+1;
        // We start from the topmost row of chunks
        chunk_row = 0;
        // Iterate all scanlines of the image
        for ( int scanline_number = 0; scanline_number < output_height; scanline_number++ )
        {   // Read scanline from image

            scanline = source_image->readScanline();

            // Is scanline valid?
            if ( scanline != NULL )
            {
                // When we hit the CHUNK_SIZE row its time to close all upened chunks and open the new ones!
                if ( current_row > CHUNK_SIZE )
                {   // Track the last row, when we will put less than CHUNK_SIZE rows in the chunks
                    remaining_rows = output_height - scanline_number;
                    // Close all already opened chunks
                    if ( !chunk_images.isEmpty() )
                    {
                        foreach (int key, chunk_images.keys() )
                        {
                            processed++;
                            sendUpdate( msg.arg(processed), 2, 0, tot_chunks, processed );
                            Image::FreeImage( chunk_images.take( key ) );
                        }
                    }
                    // Now, check and open any missing chunk...
                    remaining_cols = output_width;
                    for ( chunk_col = 0; chunk_col < max_col; chunk_col++ )
                    {
                        tmp_filename = chunk_filenames.value( MAKE_CHUNK_KEY( chunk_col, chunk_row, z ) );
                        // Proceed ONLY if file is missing
                        if ( !chunkValid( tmp_filename ) )
                        {
                            tmp_image = Image::LoadImage( tmp_filename );
                            tmp_image->startWriting( qMin(CHUNK_SIZE, remaining_cols),
                                                     qMin(CHUNK_SIZE, remaining_rows) );
                            chunk_images.insert( chunk_col, tmp_image );
                        }
                        else // report as chunk processed
                        {
                            processed++;
                            sendUpdate( msg.arg(processed), 2, 0, tot_chunks, processed );
                        }
                        remaining_cols -= CHUNK_SIZE;
                        if ( please_terminate )
                            break;
                    }
                    // reset row count
                    current_row = 0;
                    // we moved to next chunk row!
                    chunk_row++;
                } // end of re-open chunks

                // Now write to all chunks...
                if ( !chunk_images.isEmpty() )
                {
                    for ( chunk_col = 0; chunk_col < max_col; chunk_col++ )
                    {
                        tmp_image = chunk_images.value( chunk_col, NULL );
                        if ( tmp_image != NULL )
                        {
                            // Get a pointer to the part of the scanline we need to save in this chunk
                            chunk_scanline = &scanline[ chunk_col * CHUNK_SIZE * COLOR_CHANNELS ];
                            tmp_image->writeScanline( chunk_scanline );
                        }
                        qApp->processEvents( QEventLoop::AllEvents );
                        if ( please_terminate )
                            break;
                    }
                }
                current_row++;
            }
            else // scanline is NULL - error!
            {                
                setErrorString( tr("Unable to read source image!") );
                // Close and remove all chunk files still open
                deallocateChunkImages();
                return false;
            }
            qApp->processEvents( QEventLoop::AllEvents );
            if ( please_terminate )
                break;
        }

        // Close and remove all chunk files still open
        deallocateChunkImages();

        qApp->processEvents( QEventLoop::AllEvents );
        if ( please_terminate )
            break;
    }
    // Close and remove all chunk files still open
    deallocateChunkImages();
    return true;
}

bool WorkerPrecacheMap::findMissing()
{
    int tot_chunks = 0;
    foreach (int zoom, chunks_rows.keys() )
        tot_chunks += chunks_rows.value(zoom,0)*chunks_cols.value(zoom,0);
    QString msg = tr("Analizing cached chunks...");
    int curr = 0;
    foreach ( QString filename, chunk_filenames )
    {
        sendUpdate( msg, 0, 0, tot_chunks, curr++ );
        if ( please_terminate )
            break;
        else if ( !chunkValid( filename ) )
        {
            sendUpdate( tr("Missing chunk detected!"), 0, 0, tot_chunks, tot_chunks );
            return true;
        }
    }
    sendUpdate( msg, 0, 0, tot_chunks, tot_chunks );
    return false;
}

bool WorkerPrecacheMap::chunkValid(const QString& cfm )
{
    QFileInfo qfi(cfm);
    // By spitting the tests we speed up a bit.
    return !qfi.exists() ? false :
            qfi.size() <= 1 ? false :
            qfi.lastModified() < source_timestamp ? false : true;
}

void WorkerPrecacheMap::deallocateChunkImages()
{
    foreach (int key, chunk_images.keys() )
        Image::FreeImage( chunk_images.take( key ) );
    if ( source_image != NULL )
        Image::FreeImage( source_image );
    source_image = NULL;
}
